package com.gls.security.server.authorization.service.impl;

import com.gls.security.server.authorization.jpa.entity.Account;
import com.gls.security.server.authorization.jpa.entity.AccountRole;
import com.gls.security.server.authorization.jpa.entity.RoleResource;
import com.gls.security.server.authorization.jpa.repository.AccountRepository;
import com.gls.security.server.authorization.service.AccountService;
import com.gls.security.server.authorization.translator.AccountTranslator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.AntPathMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.HashSet;
import java.util.Set;

/**
 * @author lizhiyong
 */
@Slf4j
@Service("userDetailsService")
@Transactional(rollbackFor = Exception.class)
public class AccountServiceImpl implements AccountService, InitializingBean {

    @Value("${spring.security.user.name}")
    private String defaultUserUsername = "user";

    @Value("${spring.security.user.password}")
    private String defaultUserPassword = "user";

    @Autowired
    private AccountRepository accountRepository;

    @Autowired
    private AccountTranslator accountTranslator;

    @Autowired
    private PasswordEncoder passwordEncoder;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account account = accountRepository.findByUsernameOrMobile(username, username);
        UserDetails userDetails = accountTranslator.translator(account);
        return userDetails;
    }

    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        boolean hasPermission = false;
        String username = getUsername(authentication);
        if (StringUtils.isEmpty(username)) {
            hasPermission = false;
        } else if (StringUtils.equals(username, defaultUserUsername)) {
            //如果用户名是admin，就永远返回true
            hasPermission = true;
        } else {
            Account account = accountRepository.findByUsername(username);
            // 读取用户所拥有权限的所有URL
            if (account != null) {
                Set<String> urls = getAllUrls(account.getAccountRoles());
                for (String url : urls) {
                    if (antPathMatcher.match(url, request.getRequestURI())) {
                        hasPermission = true;
                        break;
                    }
                }
            }
        }
        return hasPermission;
    }

    private String getUsername(Authentication authentication) {
        Object principal = authentication.getPrincipal();
        if (principal instanceof UserDetails) {
            return ((UserDetails) principal).getUsername();
        } else if (principal instanceof String) {
            return (String) principal;
        }
        return null;
    }

    private Set<String> getAllUrls(Set<AccountRole> accountRoles) {
        Set<String> urls = new HashSet<>();
        for (AccountRole accountRole : accountRoles) {
            Set<RoleResource> roleResources = accountRole.getRole().getRoleResources();
            for (RoleResource roleResource : roleResources) {
                urls.addAll(roleResource.getResource().getUrls());
            }
        }
        return urls;
    }

    @Override
    public void afterPropertiesSet() {
        String username = defaultUserUsername;
        Account account = accountRepository.findByUsername(username);
        if (account == null) {
            account = new Account();
            account.setUsername(username);
            account.setPassword(passwordEncoder.encode(defaultUserPassword));
            accountRepository.save(account);
        }
    }
}
